#include "bindinggenerator.h"

#include "../arc.h"
#include "../bindings/binding.h"
#include "../bindings/ibindingalgorithm.h"
#include "../expression.h"
#include "../transition.h"

using namespace wfnet;

BindingGenerator* bindingGenerator = 0;

BindingGenerator* BindingGenerator::getInstance()
{
    if (bindingGenerator == 0)
    {
        bindingGenerator = new BindingGenerator();
    }

    return bindingGenerator;
}

void BindingGenerator::destroy()
{
    delete bindingGenerator;
    bindingGenerator = 0;
}

void BindingGenerator::registerBindingAlgorithm(ExpressionType type,
    IBindingAlgorithm *algorithm)
{
    if (!m_algorithms.contains(type))
    {
        m_algorithms[type] = algorithm;
    }
}

void BindingGenerator::unregisterBindingAlgorithm(
    ExpressionType type)
{
    if (m_algorithms.contains(type))
    {
        IBindingAlgorithm* algorithm = m_algorithms[type];

        m_algorithms.remove(type);

        delete algorithm;
    }
}

bool BindingGenerator::bind(Binding& binding,
    const core::BasicTime& globalClock,
    core::BasicTime& proposedGlobalClock,
    const QSet<const Arc* >& inputArcs,
    const QSet<const Arc* >& outputArcs,
    const Transition* transition,
    QSet<const Arc* >& toBeCheckedArcs) const
{
    Q_ASSERT(transition != 0);

    bool ret = false;

    uint exprTypes = NONE;
    uint uExprTypes = NONE;
    bool multipleVarCase = false;

    QSet<VariableName> varOccurences;

    foreach (const Arc* arc, inputArcs)
    {
        Expression* expr = dynamic_cast<Expression* >(
            arc->getExpression());
        exprTypes |= expr->getExpressionType();

        if (expr->getExpressionType() == (uint)SIMPLE_VAL_ONLY)
        {
            toBeCheckedArcs << arc;
        }

        const QSet<VariableName> variables =
            expr->getUserVariables().keys().toSet();

        foreach (const VariableName& var, variables)
        {
            if (varOccurences.contains(var))
            {
                multipleVarCase = true;
                break;
            }
            else
            {
                varOccurences.insert(var);
            }
        }
    }

    QSet<const Arc* > oArcsUnboundVars;

    foreach (const Arc* arc, outputArcs)
    {
        Expression* expr = dynamic_cast<Expression* >(
            arc->getExpression());

        const QSet<VariableName> variables =
            expr->getUserVariables().keys().toSet();

        bool clear = true;

        foreach (const VariableName& var, variables)
        {
            if (!varOccurences.contains(var))
            {
                clear = false;
            }
        }

        if (!clear)
        {
            uExprTypes |= expr->getExpressionType();
            oArcsUnboundVars.insert(arc);
        }
    }

    Expression* guard = dynamic_cast<Expression* >(
        transition->getGuard());
    if (guard != 0)
    {
        exprTypes |= guard->getExpressionType();
    }

    if ((exprTypes & COMPLEX_VAR_VAR) == COMPLEX_VAR_VAR)
    {
        if (m_algorithms.contains(COMPLEX_VAR_VAR))
        {
            ret = m_algorithms[COMPLEX_VAR_VAR]->bind(binding, globalClock,
                proposedGlobalClock, inputArcs, transition);
        }
    }
    else if ((exprTypes & COMPLEX_VAR_VAL) == COMPLEX_VAR_VAL)
    {
        if (m_algorithms.contains(COMPLEX_VAR_VAL))
        {
            ret = m_algorithms[COMPLEX_VAR_VAL]->bind(binding, globalClock,
              proposedGlobalClock, inputArcs, transition);
        }
    }
    else if ((exprTypes & SIMPLE_VAR_ONLY) == SIMPLE_VAR_ONLY)
    {
        if (multipleVarCase)
        {
            if (m_algorithms.contains(SIMPLE_MUL_VAR))
            {
                ret = m_algorithms[SIMPLE_MUL_VAR]->bind(binding, globalClock,
                    proposedGlobalClock, inputArcs,
                    transition);
            }
        }
        else
        {
            if (m_algorithms.contains(SIMPLE_VAR_ONLY))
            {
                ret = m_algorithms[SIMPLE_VAR_ONLY]->bind(binding,
                    globalClock, proposedGlobalClock, inputArcs, transition);
            }
        }
    }
    else if ((exprTypes & SIMPLE_VAL_ONLY) == SIMPLE_VAL_ONLY)
    {
        ret = true;
    }
    else
    {
        Q_ASSERT(false); // can't have none expr. type
    }

    if (uExprTypes == SIMPLE_VAR_ONLY)
    {
        ret = ret && m_algorithms[SIMPLE_UNBOUND_VAR_ONLY]->bind(binding,
            globalClock, proposedGlobalClock, oArcsUnboundVars, transition);
    }
    else
    {
        if (uExprTypes != NONE)
        {
            Q_ASSERT(false); // not supported yet
        }
    }

    foreach (const Arc* arc, inputArcs)
    {
        Expression* expr = dynamic_cast<Expression* >(
            arc->getExpression());

        if (ret)
        {
            expr->setBinding(binding);
        }
        else
        {
            expr->clearBinding();
        }
    }

    foreach (const Arc* arc, oArcsUnboundVars)
    {
        Expression* expr = dynamic_cast<Expression* >(
            arc->getExpression());

        if (ret)
        {
            expr->setBinding(binding);
        }
        else
        {
            expr->clearBinding();
        }
    }

    return ret;
}

BindingGenerator::BindingGenerator() :
    m_algorithms()
{

}

BindingGenerator::~BindingGenerator()
{
    foreach (IBindingAlgorithm* algorithm, m_algorithms)
    {
        delete algorithm;
    }

    m_algorithms.clear();
}
